Skip to content

Module 3

React Hooks

Video Summary

When I started using React in 2015, components looked pretty different:

const Button = React.createClass({
state: {
hi: 5,
},
handleClick(event) {
console.log('Clicked!');
this.setState({ hi: this.state.hi + 1 });
},
render() {
return (
<button onClick={this.handleClick}>
{this.props.children}
</button>
);
},
});

A couple years later, we got “class components”, which looked like this:

class Button extends React.Component {
state = {
hi: 5,
};
handleClick = () => {
console.log('Clicked!');
this.setState({ hi: this.state.hi + 1 });
};
render() {
return (
<button onClick={this.handleClick}>
{this.props.children}
</button>
);
}
}

During these two eras, components were the only tool in the toolbox. Every problem in React was solved with components.

Now, components are great. It's wonderful being able to package up some markup, styles, and logic into a single reusable bundle. But not every problem should be solved this way.

For example, animations. A popular library at the time, react-motion, used a pattern known as render props. It looked like this:

import { Motion, spring } from 'react-motion';
class App extends React.Component {
render() {
return (
<Motion
defaultStyle={{ opacity: 0 }}
style={{ opacity: spring(1) }}
>
{({ opacity }) => (
<div style={{ opacity }}>
This div fades in!
</div>
)}
</Motion>
);
}
}

What in the world? Why does this Motion component take a function as children?

So, this is a way for a component to be used as a data-processor. Instead of producing some UI directly, it produces a value. We give it some data about the opacity we want at different moments in time, and it calculates the current value.

(If you haven't seen this pattern before, it probably doesn't make any sense... that's alright though, this pattern is mostly irrelevant nowadays.)

And it's not just animations. Because components were the only tool in the toolbox, we needed to use components for all kinds of things, including:

  • Pulling data from context (discussed in Module 4)
  • Network requests
  • Behaviours like scrolling to the top of the page on mount
  • Lazy-loading other components

All of these non-UI usecases led to a common scenario called Wrapper Hell:

Lots and lots of nested React elements

In order to help with these issues, the React team developed a second tool for the toolbox: hooks.

We've seen how the useState hook lets us “hook into” React state:

function Button({ children }) {
const [hi, setHi] = React.useState(5);
function handleClick(event) {
console.log('Clicked!');
setHi(hi + 1);
}
return (
<button onClick={handleClick}>
{children}
</button>
);
}

In this module, we'll learn about several other handy hooks that solve different discrete problems.

The really cool thing about hooks, though, is that they allow us to package up and re-use business logic. Like how a Button component can be sprinkled around our application, we can create custom bundles of business logic, like useMousePosition or useSpringAnimation, which can be shared between components.

Hooks are an incredible addition to React. Even though I was a huge proponent of render props and the old way of doing things, I have to admit that things are better with hooks.

Everything has tradeoffs, and some things are more complicated now than they used to be, but overall, hooks are what makes modern React so exciting and fun.

Correction: The final snippet from this video mistakenly used this within a function component. Sorry for any confusion! The code should have been:

function Button({ children }) {
const [hi, setHi] = React.useState(5);
function handleClick(event) {
console.log('Clicked!');
setHi(hi + 1);
}
return (
<button onClick={handleClick}>
{children}
</button>
);
}